ZK 5.0 and Server+Client Fusion

From Documentation
DocumentationSmall Talks2009JulyZK 5.0 and Server+Client Fusion
ZK 5.0 and Server+Client Fusion

Author
Robbie Cheng, Technology Evangelist, Potix Corporation
Date
July 14, 2009
Version
ZK 5.0

Introduction

Since ZK 5, developers can not only leverage the ease of development of the server-centric architecture, but also the full controllability of client-side programming. It's developer's choice to implement a function using either server-side or client side development according to their requirements.

In this article, we will demonstrate two different approaches, pure server-centric, and client/server development to implement a real-world application.

Resources

Download ZK 5 Live Demo Documentation

The Application: ZK Finance

ZK finance is real-world application; users can look up the historical price of stocks, and see the result in a table, and a chart. Here is the snapshot of this application, including a search box on the left panel, and a table, and chart on the right panel. 300

Pure Server-centric approach

First of all, we use the pure server-centric approach to implement this application using MVC (Model-View-Controller) pattern.

Model

There are two objects of this application, including Stock, and Price. The relationship between Stock and Price is one-to-many. Each stock has more than one Price objects to represent its historical price.

Stock.java

public class Stock {
	private int _id;
	private String _name;
	private List _priceItems = new ArrayList();
....
getter and setter methods
}

Price.java

public class Price {
	private String _date;
	private double _open;
	private double _high;
	private double _low;
	private double _close;
	private int _volumn;
....
getter and setter methods
}

Moreover, we create a DAO object which is responsible for providing data of stock. StockDAO.java

public class StockDAO {
private List stocks = new LinkedList();
public Stock getStock(int id) {}
public List findAll() {}

}

View

We provide a search function, and show the search result in a Listbox.

index.zul

<?init class="org.zkoss.zkplus.databind.AnnotateDataBinderInit"?>
<borderlayout id="main" apply="StockController">
	<west title="ZK Finance" size="250px" flex="true"
		splittable="true" minsize="210" maxsize="500" collapsible="true">
		<panel>
			<toolbar>
				<label value="Search:" />
				<textbox id="searchBox" ctrlKeys="#down#up"
					focus="true" sclass="demo-search-inp" />
			</toolbar>
			<panelchildren>
				<listbox id="itemList" model="@{main$composer.stocks}"
					fixedLayout="true" vflex="true">											
					<listitem self="@{each='stock'}" value="@{stock}">
						<listcell label="@{stock.name}" />
					</listitem>
				</listbox>
			</panelchildren>
		</panel>
	</west>
	<center>
		<include id="detail"/>
	</center>
</borderlayout>

Regarding historical price of a stock, we use Grid, and line Chart to show the result.

price.zul

<?init class="org.zkoss.zkplus.databind.AnnotateDataBinderInit"?>
<window id="main2" apply="PriceController">
	<grid id="history" model="@{main2$PriceController.prices}" >
		<columns menupopup="auto">
			<column label="Date" />
			<column label="Open" />
			<column label="High" />
			<column label="Low" />
			<column label="Close" />
			<column label="Volumn" />
		</columns>
		<rows>
			<row self="@{each='price'}">
				<label value="@{price.date}"/>
				<label value="@{price.open}"/>
				<label value="@{price.high}"/>
				<label value="@{price.low}"/>
				<label value="@{price.close}"/>
				<label value="@{price.volumn}"/>
			</row>
		</rows>
	</grid>

	<chart id="line" width="500" height="250" type="line"
		fgAlpha="128" model="@{main2$PriceController.cateModel}"/>
</window>

Controller

Search function

In StockController.java, we register onChanging event listener on Textbox component to get user’s input instantly, and then list all stocks that match the query in the Listbox component.

StockController.java

public class StockController extends GenericForwardComposer {	
	Textbox searchBox;
	Listbox itemList;
	Include detail;
	StockDAO dao = new StockDAO();
	private static String DETAIL_URL = "price.zul";
	
	public void onCreate$main(){		
		itemList.setSelectedIndex(0);		
		Events.postEvent(new Event(Events.ON_SELECT, itemList));
	}
	
	public void onSelect$itemList(){
		int id = ((Stock)itemList.getSelectedItem().getValue()).getId();
		detail.setSrc(DETAIL_URL + "?id=" + id);
	}	
	
	public void onChanging$searchBox(InputEvent event) {
		String key = event.getValue();
		LinkedList item = new LinkedList();
		List items = dao.findAll();

		if (key.trim().length() != 0) {
			for (Iterator iterator = items.iterator(); iterator.hasNext();) {
				Stock st = (Stock) iterator.next();
				if (st.getName().toLowerCase()
						.indexOf(key.toLowerCase()) != -1)
					item.add(st);
				
			}
			itemList.setModel(new ListModelList(item));
		} else itemList.setModel(new ListModelList(items));
	}
	
	public List getStocks(){
		return dao.findAll();
	}
}

Once the user clicks any Stock in the result, we register onSelect event listener to show the historical prices by reloading the price.zul page. onSelect$itemList()

Showing the result in table and chart

To show the historical prices of certain stock, in PriceController.java, we get prices data from StockDAO, and create required data model for chart component.

PriceController.java

public class PriceController extends GenericAutowireComposer {
	
	private StockDAO dao = new StockDAO();
	private CategoryModel cateModel;			
	private List items;
	
	public PriceController() {
		init();
	}
	
	public void init() {
		//get stock id
		int id = Integer.parseInt((String) Executions.getCurrent().getParameter("id"));		
		Stock stock = dao.getStock(id);
		items = stock.getPriceItems();		
		//create category model for chart
		cateModel = new SimpleCategoryModel();
		for (Iterator iterator = items.iterator(); iterator.hasNext();) {
			Price price = (Price) iterator.next();
			cateModel.setValue(stock.getName(), price.getDate(), price.getClose());
		}
	}
	public List getPrices(){
		return items;
	}
	public CategoryModel getCateModel() {
		return cateModel;
	}	
}

Server+client Fusion approach

In addition to the pure server-centric approach, we will illustrate a hybrid approach here. Let us say we want to improve the responsiveness of the search, then we can move the corresponding code to the client. In the following paragraphs, we use search function as an example.

Modification of existing code

To implement search function at client side, first of all, we have to load the stock data to client. We use an include component to load the data source, data.xml, from the server.

<include id="data" src="data.xml" comment="true"/>

Secondly, at the server side, the control code (StockController.java) shall be removed from index.zul since we will implement this function at client side.

<borderlayout id="main">

In addition, we no longer generate the stock list at the server-side so we remove code related to generating the search result, too.

<listbox id="list" rows="10" width="300px">

Client side programming

To implement the search function at client side, we create an update function in listbox where it generates stock items according to user’s input.

<zk xmlns:w="http://www.zkoss.org/2005/zk/client">
<script src="/zkau/web/js/zk.xml.wpd"/>

....
<listbox id="list" rows="10" width="300px">
	<attribute w:name="update"><![CDATA[
	(function () {
		var data;
		function loadData(w) {
			var xmlDoc = zk.xml.Utl.parseXML(jq(w).html().replace(/<!--|-->/g, '').trim()),
				ids = xmlDoc.getElementsByTagName("id"),
				labels = xmlDoc.getElementsByTagName("name");
			
			data = [];
			jq(ids).each(function (i) {
				data.push({id: this.firstChild.nodeValue, label: labels[i].firstChild.nodeValue});
			});
		}
		function createItems (listbox, data) {
			if (!data.length) return;
			jq(data).each(function () {
				listbox.appendChild(new zul.sel.Listitem({label: this.label, uuid: this.id}));
			});
		}
		return function (txt) {
			txt = txt || '';
			if (!data) loadData(this.$f('data'));
			this.clear();
			createItems(this, jq.grep(data, function (item) {
				return item.label.toLowerCase().indexOf(txt.toLowerCase()) != -1;
			}));
			this.stripe();
		};
	})()
	]]></attribute>
</listbox>
  • zk.xml.Utl.parseXML() is defined inside zk/src/archive/web/js/zk/xml/utl.js. To use it, we have to include its source by <script src="/zkau/web/js/zk.xml.wpd"/>
  • $f (id), a way to get fellow component. It is the same as getFellow(id), i.e., an alias of getFellow.
  • http://www.zkoss.org/2005/zk/client is the so-called client namespace that tells ZK engines to generate the code at the client-side instead of server-side.

Then, we register onChanging event listener on textbox to invoke the update function of listbox at clien-side, too. Notice that we have to specify the client namespace (w:), since the listener is running at the client.

<textbox id="inp" w:onChanging="this.$f('list').update(event.data.value)"/>

Besides, if we would like to show all stock items in the beginning, we have to invoke update function of listbox component at initialization by registering bind_ function to do the initialization.

<listbox id="list" rows="10" width="300px">
	<attribute w:name="bind_">
	function (desktop, skipper, after) {
		this.$bind_.apply(this, arguments);
		var self = this;
		after.push(function () {
			self.update();
			self.firstChild.setSelected(true);
		});
	}
	</attribute>
....
</listbox>

Interacting with server-side components

So far, we’ve migrated the search function to client-side, the last mile is to communicate with server to update the historical prices of selected stock item. To make sure the onSelect event will be send to server instantly, we simply register the onSelect event at listbox component. Thus, once users click any stock item, the ZK engine will sends an onSelect event to server.

<listbox id="list" onSelect="" rows="10" width="300px">
....

Next, to handle the onSelect event directly at server, we could implement a service at server side; then, we can update the content of historical price accordingly.

<listbox id="list" onSelect="" rows="10" width="300px">
....
</listbox>

<include id="content" src="price.zul?id=1"/>
<zscript>
	public class MyService implements org.zkoss.zk.au.AuService {
		public boolean service(org.zkoss.zk.au.AuRequest request, boolean everError) {
			final String cmd = request.getCommand();
			if (cmd.equals(Events.ON_SELECT)) {
				String uuid = ((List)request.getData().get("items")).get(0);
				System.out.println("selected:" + uuid);
				content.setSrc("price.zul?id=" + uuid);
				return true;
			}
			return false;
		}
	}
	list.setAuService(new MyService());
</zscript>

Summary

In above paragraphs, we demonstrate two different approached to implement the same application using either pure server-side, or a hybrid approach. Whether to use server-centric or client-centric development are developers’ choices, ZK 5 points out a new way to implement Rich Internet Application that developers can leverage both ease of development, and controllability at the same time.

Download

The application could be downloaded here.

See Also

  1. ZK Client-side Reference: General Control
  2. Client Namespace
  3. ZK 5.0 and jQuery
  4. ZK 5.0 and jQuery part 2
  5. Widget -- See the usage of $f(), $n(), bind_, init_, zk.widget.$()



Copyright © Potix Corp. This article is licensed under GNU Free Documentation License.